package mini.expr;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;

import mini.ExpressionList;
import mini.Proc;
import mini.compiler.CodeGenerator;
import mini.util.SymbolicNames;

public class FunctionCall extends Expr {

	private String funcid;
    private ExpressionList explist;
    
    // This is a hack! When we don't know how many temp/local variables to allocate, we
    // allocate "lots and lots", as defined by this number
    private static final int LOTS_AND_LOTS = 100;
    
    // Almost every function has a return value -- the exception being the main function, which
    // not only doesn't have a return value, doesn't have space for it allocated.
    private boolean hasReturnValue = true;

    public FunctionCall(String id, ExpressionList el) {
        funcid = id;
        explist = el;
    }

	public Integer eval(HashMap<String, Integer> nametable, HashMap<String, Proc> functiontable, LinkedList var) {
        return functiontable.get(funcid).apply(nametable, functiontable, var, explist);
    }
    
    @Override
    public String parse(HashMap<String, Integer> hm, HashMap<String, Proc> functiontable, LinkedList var) {
    	
    	// Here is a list of the parameters to store
    	List<Expr> expressions = (explist == null ? new ArrayList<Expr>() : explist.getExpressions() );
    	if( expressions == null ) {
    		expressions = new ArrayList<Expr>();
    	}
    	
    	//Slide 33. Initiate Call
    	CodeGenerator cg = new CodeGenerator();
    	
    	// First, I need to make sure to parse out all the expressions that will be used
    	for( Expr expr : expressions ) {
    		cg.appendCode( expr.parse(hm, functiontable, var));
    	}
    	
    	//1. Create activation record. Update FP and SP
    	// Jennifer code-reviewed step 1 and it looks right
    	//Store the existing frame pointer to the SymbolicNames.getSymbolicNameForScratch()
    	cg.addLoadAccumulator(SymbolicNames.getSymbolicNameForFP());
    	cg.addStoreAccumulator(SymbolicNames.getSymbolicNameForScratch()); //FP, SP, and SymbolicNames.getSymbolicNameForScratch() should be at beginnig of memory.
    	
    	//New frame pointer needs to be 1 cell greater than existing stack pointer
    	cg.addLoadAccumulator(SymbolicNames.getSymbolicNameForSP());
    	cg.addAdd(SymbolicNames.getSymbolicNameForInteger(hm,1));
    	cg.addStoreAccumulator(SymbolicNames.getSymbolicNameForFP());
    	
    	// Now store the new stack pointer value
    	// Since we don't know how big the activation record is, necessarily, just
    	// use a meta-instruction to say to fill it in on the second pass
    	cg.addSizeOfActivationRecord(this.funcid);
    	cg.addStoreAccumulator(SymbolicNames.getSymbolicNameForSP());
    	
    	//We can go ahead and fill in the prev FP value of the Activation record 
    	//because it will still be in SymbolicNames.getSymbolicNameForScratch() at this point.
    	cg.addSubtract(SymbolicNames.getSymbolicNameForInteger(hm,1)); //One cell before stack pointer is the prev fp of the activation record (slide 32)
    	cg.addStoreAccumulator(SymbolicNames.getSymbolicNameForFPB()); //Hmmm..... I need another SymbolicNames.getSymbolicNameForScratch(). If I put the current value in the accumulator to SymbolicNames.getSymbolicNameForScratch(), I'll lose the prev fp value
    	cg.addLoadAccumulator(SymbolicNames.getSymbolicNameForScratch());	// Scratch at this point still has our previous frame pointer value
    	cg.addStoreIndirect(SymbolicNames.getSymbolicNameForFPB()); //store in fp value of the activation record.
    	
    	//2. Store the params in activation record
    	
    	for(int i = 0; i < expressions.size(); i++) {
    		
    		// Store where we want to put the value into the FPB, so we can do a load indirect
    		cg.addLoadAccumulator(SymbolicNames.getSymbolicNameForFP());
    		cg.addAdd( SymbolicNames.getSymbolicNameForInteger(hm, i));
    		cg.addStoreAccumulator(SymbolicNames.getSymbolicNameForFPB());

    		// This is a hack. We need some way to tell the rewriter that when they rewrite
    		// what would normally be an LDA instruction (by going relative to the
    		// address in the frame pointer) they really need to go relative to the
    		// address that's in the prev fp area of memory -- cannot disturb the FPB
    		// while doing it, either!
    		cg.addLoadAccumulatorRelativeToPrevFP( expressions.get(i).getTempVariable() );
    		
    		// And store it at the parameter's location
    		cg.addStoreIndirect(SymbolicNames.getSymbolicNameForFPB());
    	}
    	
    	// The CAL instruction handles steps 3-4 on the slide, as it: a) stores
    	// the return address; and, b) jumps to the start of the function
    	cg.addCallFunction( funcid );
    	
    	// Slide 33, step 3 of "return from call": retrieve return value from activation record
    	// First, put the address in memory of where we need to load the return value from into scratch (assuming it has one)
    	if( hasReturnValue ) {
        	cg.addLoadAccumulator(SymbolicNames.getSymbolicNameForSP());  		//load SP into the accumulator		
        	cg.addSubtract(SymbolicNames.getSymbolicNameForInteger(hm,2));		//subtract 2 to get the address of memory that holds the return value
        	cg.addStoreAccumulator(SymbolicNames.getSymbolicNameForScratch());	//Scratch now holds the address of memory where we load the return value from
    	}
    	
    	// Next, set the FPB to the address in memory of what the FP will be, after we "pop" this AR and restore it
    	cg.addLoadAccumulator(SymbolicNames.getSymbolicNameForSP());		// Load SP into the accumulator
    	cg.addSubtract(SymbolicNames.getSymbolicNameForInteger(hm,1));		// Subtract 1 to get the address of memory that holds the previous FP's memory address
    	cg.addStoreAccumulator(SymbolicNames.getSymbolicNameForFPB());		// Put this address in the FPB for a moment
    	cg.addLoadIndirect(SymbolicNames.getSymbolicNameForFPB());			// Now the accumulator has the actual memory address of the previous FP
    	cg.addStoreAccumulator(SymbolicNames.getSymbolicNameForFPB());		// Which we store in the FPB for a moment
    	
    	// Set the SP to what its value should be after we "pop" this function's AR
    	// The SP we have to restore is one less than the current FP, which we have been careful not to modify
    	cg.addLoadAccumulator(SymbolicNames.getSymbolicNameForFP());		// Load address of the FP into the accumulator
    	cg.addSubtract(SymbolicNames.getSymbolicNameForInteger(hm,1));		// Subtract one from it to find out what the SP should be, after this function's AR is "popped" off the stack
    	cg.addStoreAccumulator(SymbolicNames.getSymbolicNameForSP());		// And now we set it! This is what we have to restore the SP to
    	
    	// Set the FP to what its value should be after we "pop" this function's AR
    	// Now we can set the FP to right the memory address (which we previously stored in the FPB)
    	cg.addLoadAccumulator(SymbolicNames.getSymbolicNameForFPB());		
    	cg.addStoreAccumulator(SymbolicNames.getSymbolicNameForFP());
    	
    	// Only worry about return values if one is expeted
    	if( hasReturnValue ) {
        	// In which case, load the return value into memory; remember, we stored the address of it in scratch
        	cg.addLoadIndirect(SymbolicNames.getSymbolicNameForScratch());
        	// So we can store it in its temp location (assuming it's expecting a return value)
        	cg.addStoreAccumulator( getTempVariable() );
    	}
    	
    	// For some reason, we have to add a newline
    	cg.appendNewline();
    	
    	return cg.getCode();
    }

	public boolean isHasReturnValue() {
		return hasReturnValue;
	}

	public void setHasReturnValue(boolean hasReturnValue) {
		this.hasReturnValue = hasReturnValue;
	}
}
